#include <ctype.h>
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_md5.h>

#define CAS_AP_SESSION_SHM_NAME    		"ap_session"

#define CAS_AP_COOKIE_NAME				"APAUTHID"
#define CAS_AP_UID_HEADER_NAME			"UID"
#define CAS_AP_CAS_SERVER_HEADER_NAME	"AUTH_SERVER_URL"

#define CAS_AP_CAS_SERVER_LOGIN_URI		"/login"

#define CAS_AP_SERVICE_PARAM			"service="

#define CAS_AP_TICKET					"ticket"
#define CAS_AP_TICKET_PARAM				"&ticket="
#define CAS_AP_TICKET_PARAM0			"ticket="

#define CAS_AP_GATEWAY_TICKET			"gatewayTicket"
#define CAS_AP_GATEWAY_TICKET_PARAM		"&gatewayTicket="
#define CAS_AP_GATEWAY_TICKET_PARAM0	"gatewayTicket="
#define CAS_AP_GATEWAY_PARAM_VALUE_AT	"gateway=true&"

#define CAS_AP_RESPONSE_XML_FAILURE_NODE	"<cas:authenticationFailure>"
#define CAS_AP_RESPONSE_XML_SUCCESS_NODE	"<cas:authenticationSuccess>"
#define CAS_AP_RESPONSE_XML_USER_NODE_S		"<cas:user>"
#define CAS_AP_RESPONSE_XML_USER_NODE_E		"</cas:user>"

#define CAS_AP_USER_ATTR_MAX_NUM			15
#define CAS_AP_USER_ATTR_VALUE_MAX_LEN		50
#define CAS_AP_SESSION_MAX_NUM				5000
#define CAS_AP_TICKET_MAX_LEN				40
#define CAS_AP_UID_MAX_LEN					20
#define MD5_LEN								32
#define CAS_AP_SECURITY_SIGN_HEADER_NAME	"AP_SEC_SIGN"

/*
File Name:    ngx_http_cas_ap_module.c
Description:  This is a nginx module for cas to proxy authentication.
Copyright:    DATATECH-EDU Inc.
Author:       cxiao84@hotmail.com
Version:      v1.0
Date:         2015/09/11
*/


/* -------------------------------------------------------------------------------------- */
/*用户属性设置的header头名称*/
static ngx_str_t CAS_AP_USER_ATTR_HEADER_NAMES[CAS_AP_USER_ATTR_MAX_NUM] = {
	ngx_string("CN"),
	ngx_string("SN"),
	ngx_string("MEMBEROF"),
	ngx_string("ALIAS-LIST"),
	ngx_string("MAIL"),
	ngx_string("TELEPHONENUMBER"),
	ngx_string("QUESTION"),
	ngx_string("ANSWER"),
	ngx_string("EDUPERSONSEX"),
	ngx_string("EDUPERSONCARDID"),
	ngx_string("EDUPERSONSTUDENTID"),
	ngx_string("EDUPERSONSTAFFID"),
	ngx_string("USBKEYSN"),
	ngx_string("EDUPERSONORGDN"),
	ngx_null_string,
};
/*cas返回xml用户属性开始节点*/
static ngx_str_t CAS_AP_RESPONSE_XML_NODES_S[CAS_AP_USER_ATTR_MAX_NUM] = {
	ngx_string("<cas:cn>"),
	ngx_string("<cas:sn>"),
	ngx_string("<cas:memberOf>"),
	ngx_string("<cas:alias-list>"),
	ngx_string("<cas:mail>"),
	ngx_string("<cas:telephonenumber>"),
	ngx_string("<cas:question>"),
	ngx_string("<cas:answer>"),
	ngx_string("<cas:eduPersonSex>"),
	ngx_string("<cas:eduPersonCardID>"),
	ngx_string("<cas:eduPersonStudentID>"),
	ngx_string("<cas:eduPersonStaffID>"),
	ngx_string("<cas:usbkeySN>"),
	ngx_string("<cas:eduPersonOrgDN>"),
	ngx_null_string,
};
/*cas返回xml用户属性结束节点*/
static ngx_str_t CAS_AP_RESPONSE_XML_NODES_E[CAS_AP_USER_ATTR_MAX_NUM] = {
	ngx_string("</cas:cn>"),
	ngx_string("</cas:sn>"),
	ngx_string("</cas:memberOf>"),
	ngx_string("</cas:alias-list>"),
	ngx_string("</cas:mail>"),
	ngx_string("</cas:telephonenumber>"),
	ngx_string("</cas:question>"),
	ngx_string("</cas:answer>"),
	ngx_string("</cas:eduPersonSex>"),
	ngx_string("</cas:eduPersonCardID>"),
	ngx_string("</cas:eduPersonStudentID>"),
	ngx_string("</cas:eduPersonStaffID>"),
	ngx_string("</cas:usbkeySN>"),
	ngx_string("</cas:eduPersonOrgDN>"),
	ngx_null_string,
};
/*cas返回xml用户属性开始节点的占用的长度*/
static int CAS_AP_RESPONSE_XML_NODES_S_OFFSET[CAS_AP_USER_ATTR_MAX_NUM] = {
	sizeof("<cas:cn>"),
	sizeof("<cas:sn>"),
	sizeof("<cas:memberOf>"),
	sizeof("<cas:alias-list>"),
	sizeof("<cas:mail>"),
	sizeof("<cas:telephonenumber>"),
	sizeof("<cas:question>"),
	sizeof("<cas:answer>"),
	sizeof("<cas:eduPersonSex>"),
	sizeof("<cas:eduPersonCardID>"),
	sizeof("<cas:eduPersonStudentID>"),
	sizeof("<cas:eduPersonStaffID>"),
	sizeof("<cas:usbkeySN>"),
	sizeof("<cas:eduPersonOrgDN>"),
	0,
};

/* 指向共享内存 */
static ngx_shm_zone_t	*ngx_http_cas_ap_user_session_shm_zone;
/* 用于定时清理超时的会话 */
static ngx_event_t		session_cleaner;

/* 配置结构体 */
typedef struct {
	ngx_int_t	ap_session_timeout;		/* ap会话超时时间(秒) */
	ngx_int_t	ap_session_max_size;	/* ap会话最大数量 */
	ngx_str_t	ap_cas_server_url;		/* cas服务器登陆页面地址：http://ca.dk.edu.com/cas */
	ngx_str_t	ap_base_url;			/* 被代理集成的应用的对外访问的基本url：http://app.dk.edu.com:8080 */
	ngx_str_t	ap_cookie_name;			/* 用于描述客户端与nginx代理间的会话的Cookie名称：默认APAUTHID */
	ngx_str_t	ap_uid_header;			/* 用于将用户ID传递到被代理应用的header的名称：默认UID */
	ngx_str_t	ap_security_key;		/* 加密header */
	ngx_flag_t	ap_force_to_cas;		/* 是否由nginx强制跳转至cas */
	ngx_flag_t	ap_pass_though;			/* 如果是on，本模块将不进行任何处理，直接交给nginx */
} ngx_http_cas_ap_loc_conf_t;

/* 请求上下文结构体 */
typedef struct {
	ngx_str_t	ticket;		/* ticket */
	ngx_flag_t	success;	/* 校验结果 */
	ngx_str_t	uid;		/* 用户名 */
	ngx_array_t	*attrs;		/* 用户属性数组 */
	int			attrs_size;	/* 实际用户属性数量 */
} ngx_http_cas_ap_req_ctx_t;

/* 用户会话 */
typedef struct {
	char		id[CAS_AP_TICKET_MAX_LEN]; /* 会话ID 40*/
	char		attrs[CAS_AP_USER_ATTR_MAX_NUM][CAS_AP_USER_ATTR_VALUE_MAX_LEN]; /* 用户其他属性 750*/
	char		uid[CAS_AP_UID_MAX_LEN];/* 用户ID 20*/
	int			attrs_size;/* 实际用户属性数量 4*/
	int			isAnonymous;/* 是否匿名会话 4*/
	void		*next;/* hash冲突时用来链接统一hash值的session 8*/
	void		*prev;/* hash冲突时用来链接统一hash值的session 8*/
	void		**slot; /* 指向冲突session的链表头 8*/
	time_t		ter_time;/* 会话的寿命期 8*/
}ngx_http_cas_ap_user_session_t;

/* 所有的用户会话 */
typedef struct {
	ngx_slab_pool_t					*shpool;/* 申请的共享内存池，利用它来分配内存 8*/
	ngx_http_cas_ap_user_session_t	*sessions[CAS_AP_SESSION_MAX_NUM];/* the hash table */
} ngx_http_cas_ap_user_session_list_t;


/* -------------------------------------------------------------------------------------- */

/* 设置会话数目并申请共享内存 */
static char * ngx_http_cas_ap_set_max_session_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
/* 共享内存初始化 */
static ngx_int_t ngx_http_cas_ap_shm_init(ngx_shm_zone_t *shm_zone, void *data);

/* 生成配置、合并配置 */
static void *ngx_http_cas_ap_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_cas_ap_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
/* 模块初始化 */
static ngx_int_t ngx_http_cas_ap_init(ngx_conf_t *cf);

/* 模块进程初始化，设置定时器来定时清理超时的会话 */
static ngx_int_t ngx_http_cas_ap_init_process(ngx_cycle_t *cycle);
/* 定时器执行的清理函数 */
static void ngx_http_cas_ap_session_clean(ngx_event_t *ev);

/* 字符检索 */
static int ngx_http_cas_ap_index_of(const char *str1, const char *str2);
/* 获取指定cookie */
static ngx_int_t ngx_http_cas_ap_find_cookie(ngx_http_request_t *r, ngx_str_t name, ngx_str_t *value);
/* 查找ticket或者gatewayTicket并将其在r->args中移除 */
static ngx_int_t ngx_http_cas_ap_scan_and_remove_ticket(ngx_http_request_t *r, ngx_str_t *ticket);


/* 构造子请求并设置子请求响应处理函数 */
static ngx_int_t ngx_http_cas_ap_validate_ticket(ngx_http_request_t *r, ngx_str_t *ticket);
/* 处理子请求的相应结果 */
static ngx_int_t ngx_http_cas_ap_validate_ticket_handler(ngx_http_request_t *r, void *data, ngx_int_t rc);
/* 解析cas返回的xml */
static void
ngx_http_cas_ap_parse_cas_response(ngx_str_t response, ngx_str_t *userAttr,
	const char *nodeNameStart, const char *nodeNameEnd, int startOffset);


/* 重定向 */
static ngx_int_t ngx_http_cas_ap_send_redirect(ngx_http_request_t *r, const ngx_str_t location);
/* 重定向刷新当前请求 */
static ngx_int_t ngx_http_cas_ap_send_refresh_redirect(ngx_http_request_t *r);
/* 重定向到cas登录页 */
static ngx_int_t ngx_http_cas_ap_send_login_redirect(ngx_http_request_t *r);
/* 设置AP的会话cookie */
static ngx_int_t
ngx_http_cas_ap_set_cas_ap_cookie(ngx_http_request_t *r, ngx_http_cas_ap_user_session_t *session);

/* 计算session的id的hash来确定存储下标位置 */
static ngx_int_t
ngx_http_cas_ap_cookie_hash(ngx_http_request_t *r, ngx_str_t *cookie);
/* 将用户信息存储到session中 */
static ngx_http_cas_ap_user_session_t *
ngx_http_cas_ap_store_session(ngx_http_request_t *r, ngx_flag_t isAnonymous, ngx_str_t *gatewayTicke);
/* 获取用户的session */
static ngx_http_cas_ap_user_session_t *
ngx_http_cas_ap_get_session_by_ticket(ngx_http_request_t *r, ngx_str_t *ticket);
/* 删除用户会话 */
static ngx_int_t ngx_http_cas_ap_session_delete(ngx_http_cas_ap_user_session_t *session);
/* 注销用户会话 */
static ngx_int_t ngx_http_cas_ap_session_logout(ngx_http_cas_ap_user_session_t *session);

/* 设置header */
static ngx_int_t
ngx_http_cas_ap_set_header(ngx_http_request_t *r, ngx_str_t header_name, ngx_str_t header_value);
/* 将securityKey + uid进行MD5计算，返回大写的MD5串 */
static ngx_int_t
ngx_http_cas_ap_create_security_sign(ngx_str_t securityKey, ngx_str_t uid, ngx_str_t *security_sign);
/* 根据用户session来设置被代理的后端应用header头 */
static ngx_int_t
ngx_http_cas_ap_set_header_by_session(ngx_http_request_t *r, ngx_http_cas_ap_user_session_t *session);

/* 本模块处理请求的主函数 */
static ngx_int_t ngx_http_cas_ap_handler(ngx_http_request_t *r);

/* -------------------------------------------------------------------------------------- */
/* 模块配置指令定义 */
static ngx_command_t  ngx_http_cas_ap_commands[] =
{
	{ ngx_string("ap_session_timeout"),
	NGX_HTTP_MAIN_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1,/* 只能直接配置在http配置指令里，可以不配置 默认60*60秒 */
	ngx_conf_set_num_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_session_timeout),
	NULL },

	{ ngx_string("ap_session_max_size"),
	NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,/* 只能直接配置在http配置指令里，必须配置 */
	ngx_http_cas_ap_set_max_session_size_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_session_timeout),
	NULL },

	/* cas的登陆地址 http://cas.dk.edu.dk/cas */
	{ ngx_string("ap_cas_server_url"),
	NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE1,/* 只能直接配置在http配置指令里，必须配置 */
	ngx_conf_set_str_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_cas_server_url),
	NULL },

	/* 用于标记客户端与nginx之间会话的cookie的名称 */
	{ ngx_string("ap_cookie_name"),
	NGX_HTTP_MAIN_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1,/* 只能直接配置在http配置指令里 可以不配置 默认APAUTHID */
	ngx_conf_set_str_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_cookie_name),
	NULL },

	/* 用于将用户名传递到后端APP的header的名称，每个代理可设定不同的header */
	{ ngx_string("ap_uid_header"),
	NGX_HTTP_SRV_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1,/* 只能直接配置在server配置指令里 可以不配置 默认UID*/
	ngx_conf_set_str_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_uid_header),
	NULL },

	/* 代理应用对外访问的基本URL，同时也用来匹配cas的集成应用 http://app.dk.edu.com:8080 */
	{ ngx_string("ap_base_url"),
	NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1,/* 只能直接配置在server配置指令里 必须配置 */
	ngx_conf_set_str_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_base_url),
	NULL },

	/* 是否由nginx强制跳转至cas，否则跳转动作由后端的app应用自己完成  */
	{ ngx_string("ap_force_to_cas"),
	NGX_HTTP_SRV_CONF | NGX_CONF_NOARGS | NGX_CONF_FLAG,/* 只能直接配置在server配置指令里 可以不配置 默认off */
	ngx_conf_set_flag_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_force_to_cas),
	NULL },

	/* 'ap_security_key 674a5c443cf211e1a2440019b9e04a91' 用来加密传递到后端header的值，如果设置就会加密，否则不加密 */
	{ ngx_string("ap_security_key"),
	NGX_HTTP_SRV_CONF | NGX_CONF_NOARGS | NGX_CONF_TAKE1, /* 只能在server层配置 可以不配置 默认空表示不加密 */
	ngx_conf_set_str_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_security_key),
	NULL },

	/* ap_pass_though on|off 默认off，一般情况下把一些静态资源定义在此location */
	{ ngx_string("ap_pass_though"),
	NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS | NGX_CONF_FLAG, /* 只能定义在location层，如果on，处理该location请求将不进行任何处理，直接交给nginx */
	ngx_conf_set_flag_slot,
	NGX_HTTP_LOC_CONF_OFFSET,
	offsetof(ngx_http_cas_ap_loc_conf_t, ap_pass_though),
	NULL },

	ngx_null_command
};

/* 模块context定义 */
static ngx_http_module_t  ngx_http_cas_ap_module_ctx = {
	NULL,                                  /* preconfiguration */
	ngx_http_cas_ap_init,                  /* postconfiguration */

	NULL,                                  /* create main configuration */
	NULL,                                  /* init main configuration */

	NULL,                                  /* create server configuration */
	NULL,                                  /* merge server configuration */

	ngx_http_cas_ap_create_loc_conf,         /* create location configuration */
	ngx_http_cas_ap_merge_loc_conf           /* merge location configuration */
};

/* 定义模块 */
ngx_module_t  ngx_http_cas_ap_module = {
	NGX_MODULE_V1,
	&ngx_http_cas_ap_module_ctx,			/* module context */
	ngx_http_cas_ap_commands,				/* module directives */
	NGX_HTTP_MODULE,                       /* module type */
	NULL,                                  /* init master */
	NULL,                                  /* init module */
	ngx_http_cas_ap_init_process,                                  /* init process */
	NULL,                                  /* init thread */
	NULL,                                  /* exit thread */
	NULL,                                  /* exit process */
	NULL,                                  /* exit master */
	NGX_MODULE_V1_PADDING
};

static char *
ngx_http_cas_ap_set_max_session_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	ngx_str_t	*value;
	ssize_t		shm_size;/* 申请内存的大小 */
	ngx_str_t	*shm_name;/* 共享内存名称 */
	ngx_int_t	number;

	value = (ngx_str_t *)cf->args->elts;
	number = ngx_atoi(value[1].data, value[1].len);
	if (number <= 0) {
		return "ap_session_max_size must large than 0.";
	}

	shm_name = (ngx_str_t *)ngx_palloc(cf->pool, sizeof(*shm_name));
	shm_name->len = sizeof(CAS_AP_SESSION_SHM_NAME);
	shm_name->data = (u_char *)CAS_AP_SESSION_SHM_NAME;

	shm_size = (sizeof(ngx_http_cas_ap_user_session_t)) * number;
	shm_size = shm_size + sizeof(ngx_http_cas_ap_user_session_list_t);

	/* 申请共享内存 */
	ngx_http_cas_ap_user_session_shm_zone = ngx_shared_memory_add(cf, shm_name, shm_size, &ngx_http_cas_ap_module);

	if (ngx_http_cas_ap_user_session_shm_zone == NULL) {
		return "Init shared memory failed.";
	}
	/* 设置共享内存初始化函数 */
	ngx_http_cas_ap_user_session_shm_zone->init = ngx_http_cas_ap_shm_init;

	return NGX_CONF_OK;
}

/* 共享内存初始化 */
static ngx_int_t
ngx_http_cas_ap_shm_init(ngx_shm_zone_t *shm_zone, void *data)
{
	ngx_slab_pool_t	*shpool;
	ngx_http_cas_ap_user_session_list_t	*session_list;

	/* 从共享内存中为session_list分配一段内存 */
	shpool = (ngx_slab_pool_t *)shm_zone->shm.addr;

	/* 此处是初始化的过程，无需加锁来进行分配 */
	session_list = (ngx_http_cas_ap_user_session_list_t *)ngx_slab_alloc(shpool, sizeof(ngx_http_cas_ap_user_session_list_t));
	if (session_list == NULL) {
		return NGX_ERROR;
	}
	/* 将分配到的所有地址内容初始化为0 */
	ngx_memzero(session_list, sizeof(ngx_http_cas_ap_user_session_list_t));
	session_list->shpool = shpool;/*让session_list自己可以管理共享内存 */
	shm_zone->data = session_list;/* 共享内存数据指向session_list */

	return NGX_OK;
}

/* 创建配置 */
static void *
ngx_http_cas_ap_create_loc_conf(ngx_conf_t *cf)
{
	ngx_http_cas_ap_loc_conf_t  *conf;

	conf = (ngx_http_cas_ap_loc_conf_t  *)ngx_pcalloc(cf->pool, sizeof(ngx_http_cas_ap_loc_conf_t));
	if (conf == NULL) {
		return NULL;
	}
	conf->ap_session_timeout = NGX_CONF_UNSET;
	conf->ap_force_to_cas = NGX_CONF_UNSET;
	conf->ap_pass_though = NGX_CONF_UNSET;
	return conf;
}

/* 合并配置 */
static char *
ngx_http_cas_ap_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
	ngx_http_cas_ap_loc_conf_t *prev = (ngx_http_cas_ap_loc_conf_t *)parent;
	ngx_http_cas_ap_loc_conf_t *conf = (ngx_http_cas_ap_loc_conf_t *)child;

	ngx_conf_merge_value(conf->ap_session_timeout, prev->ap_session_timeout, 3600);
	ngx_conf_merge_str_value(conf->ap_cas_server_url, prev->ap_cas_server_url, "");
	ngx_conf_merge_str_value(conf->ap_base_url, prev->ap_base_url, "");
	ngx_conf_merge_str_value(conf->ap_cookie_name, prev->ap_cookie_name, CAS_AP_COOKIE_NAME);
	ngx_conf_merge_str_value(conf->ap_uid_header, prev->ap_uid_header, CAS_AP_UID_HEADER_NAME);
	ngx_conf_merge_str_value(conf->ap_security_key, prev->ap_security_key, "");
	ngx_conf_merge_value(conf->ap_force_to_cas, prev->ap_force_to_cas, 0);
	ngx_conf_merge_value(conf->ap_pass_though, prev->ap_pass_though, 0);

	if (conf->ap_cas_server_url.len == 0) {
		return "ap_cas_server_url MUST be set!";
	}

	if (conf->ap_base_url.len == 0) {
		return "ap_base_url MUST be set!";
	}

	return NGX_CONF_OK;
}


/* 模块初始化 */
static ngx_int_t
ngx_http_cas_ap_init(ngx_conf_t *cf)
{
	ngx_http_handler_pt			*h;
	ngx_http_core_main_conf_t	*cmcf;

	/* NGX_HTTP_ACCESS_PHASE阶段插入处理器 */
	cmcf = (ngx_http_core_main_conf_t  *)ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
	h = (ngx_http_handler_pt *)ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
	if (h == NULL) {
		return NGX_ERROR;
	}
	*h = ngx_http_cas_ap_handler;

	return NGX_OK;
}

/* 模块进程初始化，设置定时器来定时清理超时的会话 */
static ngx_int_t
ngx_http_cas_ap_init_process(ngx_cycle_t *cycle)
{
	/* 定时器监视session的超时 */
	ngx_memzero(&session_cleaner, sizeof(ngx_event_t));
	session_cleaner.handler = ngx_http_cas_ap_session_clean;
	session_cleaner.log = cycle->log;
	ngx_add_timer(&session_cleaner, 1000);

	return NGX_OK;
}

/* 定时器执行的清理函数 */
static void
ngx_http_cas_ap_session_clean(ngx_event_t *ev) {
	ngx_http_cas_ap_user_session_t             *session;
	ngx_http_cas_ap_user_session_t             *next;
	ngx_http_cas_ap_user_session_list_t        *session_list;
	ngx_uint_t                     i;

	session_list = (ngx_http_cas_ap_user_session_list_t *)ngx_http_cas_ap_user_session_shm_zone->data;
	/* session_list加锁 */
	ngx_shmtx_lock(&session_list->shpool->mutex);

	for (i = 0; i < CAS_AP_SESSION_MAX_NUM; i++) {
		session = session_list->sessions[i];
		while (session) {
			next = (ngx_http_cas_ap_user_session_t *)session->next;
			if (session->ter_time <= ngx_time()) {
				ngx_log_stderr(0, "\tSession timeout: [%s].", session->id);
				ngx_http_cas_ap_session_delete(session);
			}
			session = next;
		}
	}

	ngx_shmtx_unlock(&session_list->shpool->mutex);

	ngx_add_timer(&session_cleaner, 1000);
}

/* ----------------------------------------------------------------------------------------------------------- */
/* 字符串查找 */
static int
ngx_http_cas_ap_index_of(const char *str1, const char *str2)
{
	const char  *p;
	p = ngx_strstr(str1, str2);
	if (p == NULL)
		return -1;
	else {
		return p - str1;
	}
}

/* 获取cookie */
static ngx_int_t
ngx_http_cas_ap_find_cookie(ngx_http_request_t *r, ngx_str_t name, ngx_str_t *value)
{
	ngx_int_t found;
	found = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &name, value);
	if (found == NGX_DECLINED) {
		return found;
	}
	if (value->len > 1) {
		return NGX_OK;
	}
	return NGX_ERROR;
}

/* 查找请求中的ticket或者gatewayTicket参数，并将其在r->args中移除 */
static ngx_int_t
ngx_http_cas_ap_scan_and_remove_ticket(ngx_http_request_t *r, ngx_str_t *ticket)
{
	/* ticket=ST-xxxxxx  or  gatewayTicket=GT-xxxxxx  */
	if ((
		ngx_http_arg(r, (u_char *)CAS_AP_TICKET, sizeof(CAS_AP_TICKET) - 1, ticket) == NGX_OK ||
		ngx_http_arg(r, (u_char *)CAS_AP_GATEWAY_TICKET, sizeof(CAS_AP_GATEWAY_TICKET) - 1, ticket) == NGX_OK
		)
		&& ticket->len > 3
		&& (ticket->data[0] == 'S' || ticket->data[0] == 'G')
		&& ticket->data[1] == 'T'
		&& ticket->data[2] == '-')
	{
		ngx_flag_t isGT = 0;
		if (ticket->data[0] == 'G') {
			isGT = 1;
		}
		/* remove ticket from query string */
		if (r->args.data + r->args.len == ticket->data + ticket->len) {
			if (
				(!isGT && r->args.len - (sizeof(CAS_AP_TICKET_PARAM0) - 1) == ticket->len) ||
				(isGT && r->args.len - (sizeof(CAS_AP_GATEWAY_TICKET_PARAM0) - 1) == ticket->len)
				) {
				/* ?ticket=ST-36-txkhyYnmcAeaZa3bdO6h-cas */
				/* ?gatewayTicket=GT-36-txkhyYnmcAeaZa3bdO6h-cas */
				r->args.len = 0;
			}
			else
			{
				if (!isGT) {
					/* ?abc=123&ticket=ST-36-txkhyYnmcAeaZa3bdO6h-cas */
					r->args.len = r->args.len - ticket->len - (sizeof(CAS_AP_TICKET_PARAM) - 1); /* '&ticket='的长度 */
				}
				else {
					/* ?abc=123&gatewayTicket=GT-36-txkhyYnmcAeaZa3bdO6h-cas */
					r->args.len = r->args.len - ticket->len - (sizeof(CAS_AP_GATEWAY_TICKET_PARAM) - 1); /* '&gatewayTicket='的长度 */
				}

			}
		}
		else {
			/* ?ticket=ST-36-txkhyYnmcAeaZa3bdO6h-cas&abc=123 */
			/* ?gatewayTicket=GT-36-txkhyYnmcAeaZa3bdO6h-cas&abc=123 */
			u_char *src = ticket->data + ticket->len + 1;
			u_char *dst = ticket->data - (isGT ? sizeof(CAS_AP_GATEWAY_TICKET) : sizeof(CAS_AP_TICKET));
			size_t size = (r->args.data + r->args.len) - src;
			r->args.len = ngx_cpymem(dst, src, size) - r->args.data;
		}
		if (!isGT) {
			return 1;/* ticket */
		}
		else {
			return 2;/* gatewayTicket */
		}
	}
	return 0;
}


/* 构造校验ticket的子请求并设置子请求响应处理函数 */
static ngx_int_t ngx_http_cas_ap_validate_ticket(ngx_http_request_t *r, ngx_str_t *ticket) {

	ngx_http_cas_ap_loc_conf_t *lcf;
	ngx_http_cas_ap_req_ctx_t *myctx;
	ngx_http_request_t *sr;
	ngx_http_post_subrequest_t *psr;
	ngx_str_t sub_location = ngx_string("/cas/serviceValidate");
	ngx_str_t url_args;
	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);

	/* 构造子请求的参数 */
	int escape = 0;
	/* 计算需要编码的字符数 */
	if (r->args.len > 0) {
		escape = 1/* ?*/ + ngx_escape_uri(NULL, r->args.data, r->args.len, NGX_ESCAPE_ARGS);/* xxx=yyy&ccc=ddd */
	}

	url_args.len = (sizeof(CAS_AP_SERVICE_PARAM) - 1) /* service= */
		+ lcf->ap_base_url.len /* http://c.dk.edu.com */
		+ r->uri.len; /* /sirius-convert/convert/index */
	if (r->args.len > 0) {
		url_args.len = url_args.len
			+ (sizeof("?") - 1) /* ? */
			+ r->args.len; /* xxx=yyy&ccc=ddd */
	}
	url_args.len += (sizeof(CAS_AP_TICKET_PARAM) - 1) /* &ticket= */
		+ ticket->len /* ST-781-sjsF0zLa2z4soLMcFEVH-cas */
		+ 1 /* '\0' */
		+ escape * 2;

	url_args.data = ngx_pnalloc(r->pool, url_args.len);
	if (url_args.data == NULL) {
		return NGX_ERROR;
	}

	u_char *p = url_args.data;
	p = ngx_cpymem(p, CAS_AP_SERVICE_PARAM, sizeof(CAS_AP_SERVICE_PARAM) - 1);/* service= */
	p = ngx_cpymem(p, lcf->ap_base_url.data, lcf->ap_base_url.len);/* http://c.dk.edu.com */
	p = ngx_cpymem(p, r->uri.data, r->uri.len);/* /sirius-convert/convert/index */
	if (r->args.len > 0) {
		p = (u_char *)ngx_escape_uri(p, (u_char *)"?", sizeof("?") - 1, NGX_ESCAPE_ARGS);/* ? */
		p = (u_char *)ngx_escape_uri(p, r->args.data, r->args.len, NGX_ESCAPE_ARGS);/* xxx=yyy&ccc=ddd */
	}
	p = ngx_cpymem(p, CAS_AP_TICKET_PARAM, sizeof(CAS_AP_TICKET_PARAM) - 1);/* &ticket= */
	p = ngx_cpymem(p, ticket->data, ticket->len);/* ST-781-sjsF0zLa2z4soLMcFEVH-cas */
	*p = '\0';
	url_args.len = p - url_args.data;

	/* 创建请求上下文 */
	myctx = (ngx_http_cas_ap_req_ctx_t *)ngx_http_get_module_ctx(r, ngx_http_cas_ap_module);
	if (myctx == NULL) {
		myctx = (ngx_http_cas_ap_req_ctx_t *)ngx_palloc(r->pool, sizeof(ngx_http_cas_ap_req_ctx_t));
		if (myctx == NULL)
			return NGX_ERROR;
		/* 为用户属性数组分配内存 */
		myctx->attrs = ngx_array_create(r->pool, CAS_AP_USER_ATTR_MAX_NUM, sizeof(ngx_str_t));
		/* 当前ticket */
		myctx->ticket.data = ticket->data;
		myctx->ticket.len = ticket->len;
		/* 关联上下文 */
		ngx_http_set_ctx(r, myctx, ngx_http_cas_ap_module);
	}

	/* 构造子请求 */
	psr = (ngx_http_post_subrequest_t *)ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
	if (psr == NULL) {
		return NGX_ERROR;
	}
	/* 设置请求响应处理函数以及请求上下文 */
	psr->handler = ngx_http_cas_ap_validate_ticket_handler;
	psr->data = myctx;

	ngx_int_t rc = ngx_http_subrequest(r, &sub_location, &url_args, &sr, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY);

	if (rc != NGX_OK)
		return NGX_ERROR;

	return NGX_DONE;
}

/* 处理校验ticket子请求的返回结果 */
static ngx_int_t
ngx_http_cas_ap_validate_ticket_handler(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
	ngx_http_request_t *pr = r->parent;
	ngx_http_cas_ap_req_ctx_t *myctx;
	ngx_str_t response;

	/* 获取构造子请求前设置的上下文 */
	myctx = (ngx_http_cas_ap_req_ctx_t *)ngx_http_get_module_ctx(pr, ngx_http_cas_ap_module);
	pr->headers_out.status = r->headers_out.status;
	if (r->headers_out.status == NGX_HTTP_OK) {
		ngx_buf_t *pRecvBuf = &r->upstream->buffer;
		response.data = pRecvBuf->pos;
		response.len = pRecvBuf->last - pRecvBuf->pos;
		/* 解析校验的结果 */
		if (ngx_http_cas_ap_index_of((const char *)response.data, CAS_AP_RESPONSE_XML_SUCCESS_NODE) > -1) {
			/* ticket校验成功 */
			myctx->success = 1;
			/* uid */
			ngx_http_cas_ap_parse_cas_response(response,
				&myctx->uid,
				CAS_AP_RESPONSE_XML_USER_NODE_S,
				CAS_AP_RESPONSE_XML_USER_NODE_E,
				sizeof(CAS_AP_RESPONSE_XML_USER_NODE_S) - 1);
			/* user attrs */
			ngx_uint_t i = 0;
			for (; i < CAS_AP_USER_ATTR_MAX_NUM; i++) {
				ngx_str_t start = CAS_AP_RESPONSE_XML_NODES_S[i];
				ngx_str_t end = CAS_AP_RESPONSE_XML_NODES_E[i];
				int offset = CAS_AP_RESPONSE_XML_NODES_S_OFFSET[i] - 1;
				if (start.len == 0) {
					break;
				}
				ngx_str_t *userAttr = (ngx_str_t *)ngx_array_push(myctx->attrs);
				ngx_http_cas_ap_parse_cas_response(response, userAttr,
					(const char *)start.data, (const char *)end.data, offset);
			}
			myctx->attrs_size = i;
		}
		else if (
			ngx_http_cas_ap_index_of((const char *)response.data, CAS_AP_RESPONSE_XML_FAILURE_NODE) > -1
			) {
			/* ticket校验失败 */
			myctx->success = 0;
		}
	}
	/* 返回NGX_OK，这时将唤醒主请求 */
	return NGX_OK;
}

/* 解析校验ticket时cas返回的xml的节点信息 */
static void
ngx_http_cas_ap_parse_cas_response(ngx_str_t response, ngx_str_t *userAttr,
	const char *nodeNameStart, const char *nodeNameEnd, int startOffset)
{
	int start = ngx_http_cas_ap_index_of((const char *)response.data, nodeNameStart);
	if (start >= 0) {
		start = start + startOffset;
		int end = ngx_http_cas_ap_index_of((const char *)response.data, nodeNameEnd);
		userAttr->data = response.data + start;
		userAttr->len = end - start;
	}
	else {
		userAttr->data = '\0';
		userAttr->len = 0;
	}
}


/* 重定向 */
static ngx_int_t
ngx_http_cas_ap_send_redirect(ngx_http_request_t *r, const ngx_str_t location)
{
	ngx_table_elt_t *loc;

	loc = r->headers_out.location = (ngx_table_elt_t *)ngx_list_push(&r->headers_out.headers);
	if (loc == NULL) {
		return NGX_ERROR;
	}

	loc->key.data = (u_char *) "Location";
	loc->key.len = sizeof("Location") - 1;
	loc->value = location;
	loc->hash = 1;

	return NGX_HTTP_MOVED_TEMPORARILY;
}

/* 重定向刷新当前请求 */
static ngx_int_t
ngx_http_cas_ap_send_refresh_redirect(ngx_http_request_t *r)
{
	ngx_http_cas_ap_loc_conf_t *lcf;
	ngx_str_t location;

	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);

	location.len = lcf->ap_base_url.len/* http://c.dk.edu.com */
		+ r->uri.len;/* /sirius-convert/convert/index */
	if (r->args.len > 0) {
		location.len += (sizeof("?") - 1) /* ? */
			+ r->args.len;/* xxx=yyy&ccc=ddd */
	}
	location.len += 1;/* \0 */

	location.data = ngx_pnalloc(r->pool, location.len);

	u_char *p = location.data;
	p = ngx_cpymem(p, lcf->ap_base_url.data, lcf->ap_base_url.len);/* http://c.dk.edu.com */
	p = ngx_cpymem(p, r->uri.data, r->uri.len);/* /sirius-convert/convert/index */
	if (r->args.len > 0) {
		p = ngx_cpymem(p, "?", sizeof("?") - 1);/* ? */
		p = ngx_cpymem(p, r->args.data, r->args.len);/* xxx=yyy&ccc=ddd */
	}
	*p = '\0';
	location.len = p - location.data;

	return ngx_http_cas_ap_send_redirect(r, location);
}

/* 将用户重定向到CAS登陆页面 */
static ngx_int_t
ngx_http_cas_ap_send_login_redirect(ngx_http_request_t *r)
{
	ngx_http_cas_ap_loc_conf_t *lcf;
	ngx_str_t location;

	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);

	/* 计算需要编码的字符数 */
	int escape = 1 /* ?*/ + ngx_escape_uri(NULL, r->args.data, r->args.len, NGX_ESCAPE_ARGS);

	location.len = lcf->ap_cas_server_url.len; /* http://cas.dk.edu.com/cas */
	location.len += (sizeof(CAS_AP_CAS_SERVER_LOGIN_URI) - 1);/* /login */
	location.len += (sizeof("?") - 1); /* ? */
	if (!lcf->ap_force_to_cas) {
		/* 要求强制跳转到cas进行认证 */
		location.len += (sizeof(CAS_AP_GATEWAY_PARAM_VALUE_AT) - 1); /* gateway=true& */
	}
	location.len += (sizeof(CAS_AP_SERVICE_PARAM) - 1 /* service= */
		+ lcf->ap_base_url.len/* http://c.dk.edu.com */
		+ r->uri.len/* /sirius-convert/convert/index */
		+ (sizeof("?") - 1) /* ? */
		+ r->args.len/* xxx=yyy&ccc=ddd */
		+ 1/* \0 */
		+ escape * 2);/* 编码字符是由1个字符变成3个字符 */

	location.data = ngx_pnalloc(r->pool, location.len);

	if (location.data == NULL) {
		return NGX_ERROR;
	}

	u_char *p = location.data;
	p = ngx_cpymem(p, lcf->ap_cas_server_url.data, lcf->ap_cas_server_url.len); /* http://cas.dk.edu.com/cas */
	p = ngx_cpymem(p, CAS_AP_CAS_SERVER_LOGIN_URI, sizeof(CAS_AP_CAS_SERVER_LOGIN_URI) - 1);/* /login */
	p = ngx_cpymem(p, "?", sizeof("?") - 1);/* ? */
	if (!lcf->ap_force_to_cas) {
		/* 要求强制跳转到cas进行认证 */
		p = ngx_cpymem(p, CAS_AP_GATEWAY_PARAM_VALUE_AT, sizeof(CAS_AP_GATEWAY_PARAM_VALUE_AT) - 1);/* gateway=true& */
	}
	p = ngx_cpymem(p, CAS_AP_SERVICE_PARAM, sizeof(CAS_AP_SERVICE_PARAM) - 1);/* service= */
	p = ngx_cpymem(p, lcf->ap_base_url.data, lcf->ap_base_url.len);/* http://c.dk.edu.com */
	p = ngx_cpymem(p, r->uri.data, r->uri.len);/* /sirius-convert/convert/index */
	if (r->args.len > 0) {
		p = (u_char *)ngx_escape_uri(p, (u_char *)"?", sizeof("?") - 1, NGX_ESCAPE_ARGS);/* ? */
		p = (u_char *)ngx_escape_uri(p, r->args.data, r->args.len, NGX_ESCAPE_ARGS);/* xxx=yyy&ccc=ddd */
	}

	*p = '\0';
	location.len = p - location.data;
	ngx_log_stderr(0, "\tSend_redirect: [%V]", &location);
	return ngx_http_cas_ap_send_redirect(r, location);
}

/* 设置AP的会话cookie */
static ngx_int_t
ngx_http_cas_ap_set_cas_ap_cookie(ngx_http_request_t *r, ngx_http_cas_ap_user_session_t *session)
{
	ngx_http_cas_ap_loc_conf_t *lcf;
	u_char	*cookie;

	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);
	cookie = ngx_pcalloc(r->pool, (lcf->ap_cookie_name.len + 2 + CAS_AP_TICKET_MAX_LEN + ngx_strlen("; Path=/; HttpOnly")));
	if (session == NULL) {
		ngx_sprintf(cookie, "%V=%s; Path=/; HttpOnly", &lcf->ap_cookie_name, "\0");
	}
	else {
		ngx_sprintf(cookie, "%V=%s; Path=/; HttpOnly", &lcf->ap_cookie_name, session->id);
	}
	ngx_table_elt_t	*header;
	header = (ngx_table_elt_t *)ngx_list_push(&r->headers_out.headers);
	if (header == NULL) {
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}
	header->hash = r->header_hash;
	header->key.len = sizeof("Set-Cookie") - 1;
	header->key.data = (u_char *) "Set-Cookie";
	header->value.len = ngx_strlen(cookie);
	header->value.data = cookie;
	return NGX_OK;
}

/* 计算session的id的hash来确定存储下标位置 */
static ngx_int_t
ngx_http_cas_ap_cookie_hash(ngx_http_request_t *r, ngx_str_t *cookie)
{
	unsigned long h = 0, g, ha;
	ngx_http_core_srv_conf_t *cscf;
	u_char *k;
	ngx_uint_t len;

	cscf = (ngx_http_core_srv_conf_t *)ngx_http_get_module_srv_conf(r, ngx_http_core_module);

	len = cookie->len + cscf->server_name.len;

	k = ngx_pcalloc(r->pool, len);
	if (!k) {
		return 0;
	}

	ngx_memcpy(k, cookie->data, cookie->len);
	ngx_memcpy(k + cookie->len, cscf->server_name.data, cscf->server_name.len);

	while (len) {
		h = (h << 4) + *k++;
		g = h & 0xf0000000l;
		if (g) h ^= g >> 24;
		h &= ~g;

		len--;
	}

	ha = h % CAS_AP_SESSION_MAX_NUM;

	return ha;
}

/* 将用户信息存储到session中 */
static ngx_http_cas_ap_user_session_t *
ngx_http_cas_ap_store_session(ngx_http_request_t *r, ngx_flag_t isAnonymous, ngx_str_t *gatewayTicke)
{
	ngx_http_cas_ap_user_session_t	*session, *tmp;
	ngx_http_cas_ap_user_session_list_t	*session_list;
	ngx_int_t hash;
	ngx_http_cas_ap_loc_conf_t *lcf;

	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);

	session_list = (ngx_http_cas_ap_user_session_list_t *)ngx_http_cas_ap_user_session_shm_zone->data;
	/* session_list加锁 */
	ngx_shmtx_lock(&session_list->shpool->mutex);
	/* 为session分配内存 */
	session = (ngx_http_cas_ap_user_session_t *)ngx_slab_alloc_locked(session_list->shpool, sizeof(ngx_http_cas_ap_user_session_t));
	if (session == NULL) {
		return NULL;
	}

	ngx_memzero(session, sizeof(ngx_http_cas_ap_user_session_t));

	/* 非匿名用户 */
	if (isAnonymous == 0)
	{
		ngx_http_cas_ap_req_ctx_t *myctx;
		myctx = (ngx_http_cas_ap_req_ctx_t *)ngx_http_get_module_ctx(r, ngx_http_cas_ap_module);
		/* session id */
		ngx_memcpy(session->id, myctx->ticket.data, myctx->ticket.len);
		/* uid */
		ngx_memcpy(session->uid, myctx->uid.data, myctx->uid.len);

		/* attrs */
		ngx_str_t *elts = (ngx_str_t *)myctx->attrs->elts;
		int i = 0;
		for (; i < myctx->attrs_size; i++) {
			ngx_memcpy(session->attrs[i], (elts + i)->data, (elts + i)->len);
		}
		session->attrs_size = i;
		hash = ngx_http_cas_ap_cookie_hash(r, &myctx->ticket);
	}
	else {
		/* session id */
		ngx_memcpy(session->id, gatewayTicke->data, gatewayTicke->len);
		hash = ngx_http_cas_ap_cookie_hash(r, gatewayTicke);
	}

	session->isAnonymous = isAnonymous;
	/* 设定寿命 */
	session->ter_time = ngx_time() + lcf->ap_session_timeout;

	/* hang session to hash table */
	if (session_list->sessions[hash]) {
		/* hash冲突 */
		tmp = session_list->sessions[hash];
		while (tmp->next) {
			tmp = (ngx_http_cas_ap_user_session_t *)tmp->next;
		}
		tmp->next = session;
		session->prev = tmp;
		session->next = NULL;
	}
	else {
		session_list->sessions[hash] = session;
		session->next = NULL;
		session->prev = NULL;
		session->slot = (void **)(&(session_list->sessions[hash]));
	}

	ngx_shmtx_unlock(&session_list->shpool->mutex);
	return session;

}

/* 获取用户的session */
static ngx_http_cas_ap_user_session_t *
ngx_http_cas_ap_get_session_by_ticket(ngx_http_request_t *r, ngx_str_t *ticket)
{
	ngx_http_cas_ap_user_session_t                  *session;
	ngx_http_cas_ap_user_session_list_t             *session_list;
	ngx_int_t										hash;

	/* session列表 */
	session_list = (ngx_http_cas_ap_user_session_list_t *)ngx_http_cas_ap_user_session_shm_zone->data;
	/* 计算ticket的hash */
	hash = ngx_http_cas_ap_cookie_hash(r, ticket);

	/* 直接根据下标取session */
	session = session_list->sessions[hash];
	if (!session) {
		return NULL;
	}
	while (session) {
		/* key匹配 */
		if (ngx_memcmp(session->id, ticket->data, ticket->len) == 0 /* && !session->des > 0*/) {
			return session;
		}
		session = (ngx_http_cas_ap_user_session_t *)session->next;
	}
	return NULL;
}

/* 删除用户会话 */
static ngx_int_t
ngx_http_cas_ap_session_delete(ngx_http_cas_ap_user_session_t *session)
{
	ngx_http_cas_ap_user_session_list_t	*session_list;

	session_list = (ngx_http_cas_ap_user_session_list_t *)ngx_http_cas_ap_user_session_shm_zone->data;

	if (!session->next && !session->prev) {
		/* the only node in the chain */
		*(session->slot) = NULL;
	}
	else if (!session->prev && session->next) {
		/* first node in the chain */
		*(session->slot) = session->next;
		((ngx_http_cas_ap_user_session_t *)(session->next))->prev = NULL;
		((ngx_http_cas_ap_user_session_t *)(session->next))->slot = session->slot;
	}
	else if (session->next && session->prev) {
		/* node in the middle */
		((ngx_http_cas_ap_user_session_t *)(session->prev))->next = session->next;
		((ngx_http_cas_ap_user_session_t *)(session->next))->prev = session->prev;
	}
	else if (session->prev && !session->next) {
		/* last node in the chain */
		((ngx_http_cas_ap_user_session_t *)(session->prev))->next = NULL;
	}

	session->next = session->prev = NULL;

	/* 释放session占用的内存 */
	ngx_slab_free_locked(session_list->shpool, session);

	return NGX_OK;
}

/* 注销用户会话 */
static ngx_int_t
ngx_http_cas_ap_session_logout(ngx_http_cas_ap_user_session_t *session)
{
	ngx_http_cas_ap_user_session_list_t	*session_list;

	session_list = (ngx_http_cas_ap_user_session_list_t *)ngx_http_cas_ap_user_session_shm_zone->data;
	/* session_list加锁 */
	ngx_shmtx_lock(&session_list->shpool->mutex);

	ngx_http_cas_ap_session_delete(session);

	/* session_list解锁 */
	ngx_shmtx_unlock(&session_list->shpool->mutex);

	return NGX_OK;
}

/* 设置header */
static ngx_int_t
ngx_http_cas_ap_set_header(ngx_http_request_t *r, ngx_str_t header_name, ngx_str_t header_value)
{
	ngx_table_elt_t	*header;
	header = (ngx_table_elt_t *)ngx_list_push(&r->headers_in.headers);
	if (header != NULL) {
		header->hash = r->header_hash;
		header->key.len = header_name.len;
		header->key.data = header_name.data;
		header->value.len = header_value.len;
		header->value.data = header_value.data;
		return NGX_OK;
	}
	return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

/* 将securityKey + uid进行MD5计算，返回大写的MD5串 */
static ngx_int_t
ngx_http_cas_ap_create_security_sign(ngx_str_t securityKey, ngx_str_t uid, ngx_str_t *security_sign)
{
	ngx_md5_t	md5_state;
	u_char		md5_digest[16];
	u_char		hex_output[16 * 2 + 1], source[512];
	ngx_uint_t	source_len = 0, i;

	ngx_memset(source, 0, 512);
	ngx_memcpy(source, securityKey.data, securityKey.len);
	source_len += securityKey.len;

	ngx_memcpy(source + source_len, uid.data, uid.len);
	source_len += uid.len;

	ngx_md5_init(&md5_state);
	ngx_md5_update(&md5_state, (void *)source, source_len);
	ngx_md5_final(md5_digest, &md5_state);

	for (i = 0; i < 16; i++) {
		sprintf((char *)hex_output + i * 2, "%02x", md5_digest[i]);
	}

	/* 大小写转换 */
	for (i = 0; i < MD5_LEN; i++) {
		u_char upper = ngx_toupper(*(hex_output + i));
		ngx_memcpy(security_sign->data + i, &upper, 1);
	}

	security_sign->data[MD5_LEN] = 0;
	security_sign->len = MD5_LEN;

	return NGX_OK;
}

/* 根据用户session来设置被代理的后端应用header头 */
static ngx_int_t
ngx_http_cas_ap_set_header_by_session(ngx_http_request_t *r, ngx_http_cas_ap_user_session_t *session)
{
	ngx_http_cas_ap_loc_conf_t *lcf;
	ngx_str_t header_name;
	ngx_str_t header_value;

	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);

	/* 设置cas server的地址到header中去 */
	header_name.data = (u_char*)CAS_AP_CAS_SERVER_HEADER_NAME;
	header_name.len = sizeof(CAS_AP_CAS_SERVER_HEADER_NAME) - 1;
	if (ngx_http_cas_ap_set_header(r, header_name, lcf->ap_cas_server_url) != NGX_OK) {
		return NGX_HTTP_INTERNAL_SERVER_ERROR;
	}

	if (session->isAnonymous) {
		/* 匿名用户会话将UID头设置为空 */
		header_value.data = NULL;
		header_value.len = 0;
		return ngx_http_cas_ap_set_header(r, lcf->ap_uid_header, header_value);
	}

	/* 非匿名要设置用户UID及其属性 */
	header_value.data = (u_char *)session->uid;
	header_value.len = ngx_strlen(session->uid);
	if (ngx_http_cas_ap_set_header(r, lcf->ap_uid_header, header_value) == NGX_OK) {
		/* 设置安全校验的header */
		if (lcf->ap_security_key.len > 0) {
			header_name.data = (u_char*)CAS_AP_SECURITY_SIGN_HEADER_NAME;
			header_name.len = sizeof(CAS_AP_SECURITY_SIGN_HEADER_NAME) - 1;
			ngx_str_t security_sign;
			security_sign.data = ngx_pnalloc(r->pool, MD5_LEN);
			ngx_http_cas_ap_create_security_sign(lcf->ap_security_key, header_value, &security_sign);
			if (ngx_http_cas_ap_set_header(r, header_name, security_sign) != NGX_OK) {
				return NGX_HTTP_INTERNAL_SERVER_ERROR;
			}
		}
		/* 设置其他属性 */
		int s = 0;
		for (; s < session->attrs_size; s++) {
			char *userAttr = session->attrs[s];
			if (ngx_strlen(userAttr)> 0) {
				header_value.len = ngx_strlen(userAttr);
				header_value.data = (u_char *)userAttr;
				header_name = CAS_AP_USER_ATTR_HEADER_NAMES[s];
				if (ngx_http_cas_ap_set_header(r, header_name, header_value) != NGX_OK) {
					return NGX_HTTP_INTERNAL_SERVER_ERROR;
				}
			}
		}
		return NGX_OK;
	}
	return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

/* 主处理函数 */
static
ngx_int_t ngx_http_cas_ap_handler(ngx_http_request_t *r)
{
	ngx_http_cas_ap_loc_conf_t *lcf;
	ngx_http_cas_ap_req_ctx_t *myctx;
	ngx_str_t cookie;
	ngx_str_t ticket;
	int f, t;
	ngx_int_t result;

	lcf = (ngx_http_cas_ap_loc_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_cas_ap_module);

	/* 不进行任何拦截 */
	if (lcf->ap_pass_though) {
		ngx_log_stderr(0, "\tap_pass_though--> %V", &r->uri);
		return NGX_OK;
	}

	myctx = (ngx_http_cas_ap_req_ctx_t *)ngx_http_get_module_ctx(r, ngx_http_cas_ap_module);

	if (myctx != NULL) {
		ngx_log_stderr(0, "\tMain request be awakened...");
		/* 说明是子请求处理结束后唤醒的主请求 */
		if (myctx->success == 1) {
			/* 以ticket作为key将用户信息存储起来 */
			ngx_log_stderr(0, "\tTicket validate successfully, now we will put it into the session.");
			ngx_http_cas_ap_user_session_t *session = ngx_http_cas_ap_store_session(r, 0, NULL);
			if (session == NULL) {
				return NGX_HTTP_INTERNAL_SERVER_ERROR;
			}

			/* 设置cookie */
			ngx_log_stderr(0, "\tCreate new session successfully, now we will set CAS_AP_COOKIE.");
			result = ngx_http_cas_ap_set_cas_ap_cookie(r, session);
			if (result != NGX_OK) {
				return result;
			}

			/* 重定向当前地址，以移除客户端浏览器地址栏中ticket参数 */
			ngx_log_stderr(0, "\tSet CAS_AP_COOKIE successfully, we will refresh to remove ticket param.");
			result = ngx_http_cas_ap_send_refresh_redirect(r);
			ngx_log_stderr(0, "END\n");
			return result;
		}
		else if (myctx->success == 0) {
			/* ticket校验失败 */
			myctx->success = 0;
			ngx_log_stderr(0, "\tTicket validate is failed!");
			ngx_log_stderr(0, "END\n");
			return NGX_HTTP_UNAUTHORIZED;
		}
	}

	ngx_log_stderr(0, "START");

	/* 是否含有destroyGatewayTicket参数，表示匿名会话的注销，注意发起该请求的是CAS服务器！！！ */
	ngx_str_t destroyGatewayTicket;
	if (ngx_http_arg(r, (u_char *)"destroyGatewayTicket", sizeof("destroyGatewayTicket") - 1, &destroyGatewayTicket) == NGX_OK) {
		ngx_http_cas_ap_user_session_t *session = ngx_http_cas_ap_get_session_by_ticket(r, &destroyGatewayTicket);
		ngx_log_stderr(0, "\tDestroy Gateway Ticket: %V", &destroyGatewayTicket);
		if (session != NULL) {
			ngx_http_cas_ap_session_logout(session);
			ngx_log_stderr(0, "\tDestroy Gateway Ticket successfully!");
		}
		else {
			ngx_log_stderr(0, "\tDestroy Gateway Ticket: [%V] NOT be found!", &destroyGatewayTicket);
		}
		ngx_log_stderr(0, "END\n");
		/* 这里注销会话后直接返回给cas即可，但是这里无法返回200，暂时使用302替代，这样cas不会出现警告 */
		return NGX_HTTP_MOVED_TEMPORARILY;
	}

	/* 是否含有logoutSessionKey参数，表示非匿名会话的注销，注意发起该请求的是CAS服务器！！！ */
	ngx_str_t logoutSessionKey;
	if (ngx_http_arg(r, (u_char *)"logoutSessionKey", sizeof("logoutSessionKey") - 1, &logoutSessionKey) == NGX_OK) {
		ngx_http_cas_ap_user_session_t *session = ngx_http_cas_ap_get_session_by_ticket(r, &logoutSessionKey);
		ngx_log_stderr(0, "\tLogout Session Key: [%V]", &logoutSessionKey);
		if (session != NULL) {
			ngx_http_cas_ap_session_logout(session);
			ngx_log_stderr(0, "\tLogout Session Key successfully!");
		}
		else {
			ngx_log_stderr(0, "\tLogout Session Key: [%V] NOT be found!", &logoutSessionKey);
		}
		ngx_log_stderr(0, "END\n");
		/* 这里注销会话后直接返回给cas即可，但是这里无法返回200，暂时使用302替代，这样cas不会出现警告 */
		return NGX_HTTP_MOVED_TEMPORARILY;
	}

	/* 判断请求中是否包含ticket或者gatewayTicket参数 */
	t = ngx_http_cas_ap_scan_and_remove_ticket(r, &ticket);
	if (t == 1) {
		/* 如果有ticket，那么要发起请求到cas去校验ticket */
		ngx_log_stderr(0, "\tFound ticket, we will call CAS API to validate.");
		return ngx_http_cas_ap_validate_ticket(r, &ticket);
	}
	else if (t == 2) {
		ngx_log_stderr(0, "\tFound gatewayTicket.");
		/* gatewayTicket表示匿名用户，直接创建匿名用户的session */
		ngx_http_cas_ap_user_session_t *session = ngx_http_cas_ap_store_session(r, 1, &ticket);
		if (session == NULL) {
			return NGX_HTTP_INTERNAL_SERVER_ERROR;
		}

		/* 将匿名用户session放入到cookie中 */
		result = ngx_http_cas_ap_set_cas_ap_cookie(r, session);
		if (result != NGX_OK) {
			return result;
		}
		/* 重定向当前地址，以移除客户端浏览器地址栏中gatewayTicket参数 */
		ngx_log_stderr(0, "\tSet CAS_AP_COOKIE<Anonymous> successfully, we will refresh to remove gatewayTicket param.");
		result = ngx_http_cas_ap_send_refresh_redirect(r);
		ngx_log_stderr(0, "END\n");
		return result;
	}

	/* 查找cookie */
	f = ngx_http_cas_ap_find_cookie(r, lcf->ap_cookie_name, &cookie);
	if (f != NGX_OK) {
		/* 没有找到与代理之间的会话cookie，则要求重定向到cas */
		ngx_log_stderr(0, "\tNo CAS_AP_COOKIE be found and redirect to cas login page!");
		result = ngx_http_cas_ap_send_login_redirect(r);
		ngx_log_stderr(0, "END\n");
		return result;
	}
	else {
		ngx_log_stderr(0, "\tCAS_AP_COOKIE has been found!");
		/* 根据找到的cookie获取会话，并从会话中获取用户信息设置到header里面去 */
		ngx_http_cas_ap_user_session_t *session = ngx_http_cas_ap_get_session_by_ticket(r, &cookie);
		if (session == NULL) {
			ngx_log_stderr(0, "\tSession NOT be found, we will refresh to remove CAS_AP_COOKIE!");
			result = ngx_http_cas_ap_set_cas_ap_cookie(r, NULL);
			if (result != NGX_OK) {
				return result;
			}
			result = ngx_http_cas_ap_send_refresh_redirect(r);
			ngx_log_stderr(0, "END\n");
			return result;
		}
		else {
			if (session->isAnonymous == 1) {
				/* 匿名用户不设置header */
				ngx_log_stderr(0, "\tSession has been found by CAS_AP_COOKIE<Anonymous>!");
			}
			else {
				ngx_log_stderr(0, "\tSession has been found by CAS_AP_COOKIE!");
				ngx_log_stderr(0, "\tSet user info into the headers in.");
			}
			result = ngx_http_cas_ap_set_header_by_session(r, session);
			ngx_log_stderr(0, "END\n");
			return result;
		}

	}
}
